//
//  JsonHelper.swift
//  iUtil
//
//  Created by wendachuan on 19/3/22.
//  Copyright © 2019年 wendachuan. All rights reserved.
//

import Foundation

/// json助手类
open class JsonHelper {
    
    /// 确保给定对象是T类型
    ///
    /// - parameter object: 待分析的对象
    /// - parameter asType: object鉴定类型
    /// - parameter error: 返回的错误
    /// - returns: 如果给定对象是T类型，则返回T类型的对象;否则返回nil
    public static func require<T>(_ object: Any?, asType: T.Type, error: NSErrorPointer) -> T? {
        if let noneNilObject = object {
            if let objectWithType = noneNilObject as? T {
                return objectWithType
            } else {
                error?.pointee = NSError(errorCode: JsonErrorCode.jsonTypeMissMatch.rawValue, errorDescription: "对象不是\(T.self)类型")
            }
        } else {
            error?.pointee = NSError(errorCode: JsonErrorCode.jsonObjectIsNil.rawValue, errorDescription: "对象为nil")
        }
        return nil
    }
    
    /// 确保给定字段在字典中存在且是指定类型
    ///
    /// - parameter dict: 给定字典
    /// - parameter field: 字段名称
    /// - parameter asType: object鉴定类型
    /// - parameter parser: 解析器
    /// - parameter error: 返回的错误
    /// - returns: 如果给定对象是T类型，则返回T类型的对象;否则返回nil
    public static func require<T>(_ dict: NSDictionary, field: String, asType: T.Type, parser: JsonParserProtocol.Type = JsonObjectParser.self, error: NSErrorPointer) -> T? {
        if let object = dict.object(forKey: field) {
            if let objectWithType = object as? T {
                return objectWithType
            } else {
                if let convertedObject = parser.parseObject(object: object, asType: asType) {
                    return convertedObject
                }
                error?.pointee = NSError(errorCode: JsonErrorCode.jsonTypeMissMatch.rawValue, errorDescription: "字段\(field)不是\(T.self)类型")
            }
        } else {
            error?.pointee = NSError(errorCode: JsonErrorCode.jsonFieldMissing.rawValue, errorDescription: "缺少字段\(field)")
        }
        return nil
    }
    
    /// 确保给定路径在字典中存在且是指定类型
    ///
    /// - parameter dict: 给定字典
    /// - parameter xpath: 路径(比如 childNode/GrandsonNode)
    /// - parameter asType: object鉴定类型
    /// - parameter parser: 解析器
    /// - parameter error: 返回的错误
    /// - returns: 如果给定对象是T类型，则返回T类型的对象;否则返回nil
    public static func require<T>(_ dict: NSDictionary, xpath: String, asType: T.Type, parser: JsonParserProtocol.Type = JsonObjectParser.self, error: NSErrorPointer) -> T? {
        var currentDict = dict
        let fields = xpath.split(separator: "/")
        for i in 0..<fields.count-1 {
            if let fieldValueDict = require(currentDict, field: String(fields[i]), asType: NSDictionary.self, parser: parser, error: error) {
                currentDict = fieldValueDict
            } else {
                return nil
            }
        }
        return require(currentDict, field: String(fields.last!), asType: asType, error: error)
    }
    
    /// 确保给定路径列表在字典中存在且是指定类型
    ///
    /// - parameter dict: 给定字典
    /// - parameter xpaths: 路径列表(比如 [childNode/GrandsonNode])
    /// - parameter asType: object鉴定类型
    /// - parameter parser: 解析器
    /// - parameter error: 返回的错误
    /// - returns: 如果给定对象是T类型，则返回T类型的对象;否则返回nil
    public static func require<T>(_ dict: NSDictionary, xpaths: [String], asType: T.Type, parser: JsonParserProtocol.Type = JsonObjectParser.self, error: NSErrorPointer) -> T? {
        for xpath in xpaths {
            if let obj = self.require(dict, xpath: xpath, asType: asType, parser: parser, error: error) {
                return obj
            }
        }
        return nil
    }
    
    /// 确保给定字段列表中至少有一个字段在字典中存在且是指定类型
    ///
    /// - parameter dict: 给定字典
    /// - parameter field: 字段名称
    /// - parameter asType: object鉴定类型
    /// - parameter parser: 解析器
    /// - parameter error: 返回的错误
    /// - returns: 如果给定对象是T类型，则返回T类型的对象;否则返回nil
    public static func require<T>(_ dict: NSDictionary, fields: [String], asType: T.Type, parser: JsonParserProtocol.Type = JsonObjectParser.self, error: NSErrorPointer) -> T? {
        for field in fields {
            if let convertedObject = require(dict, field: field, asType: asType, parser: parser, error: error) {
                return convertedObject
            }
        }
        
        error?.pointee = NSError(errorCode: JsonErrorCode.jsonFieldMissing.rawValue, errorDescription: "缺少字段\(fields)")
        return nil
    }
    
    /// 确保给定字典是指定对象类型
    ///
    /// - parameter dict: 给定字典
    /// - parameter asType: object鉴定类型
    /// - parameter error: 返回的错误
    /// - returns: 如果指定的字典是T类型，则返回T类型的对象;否则返回nil
    public static func requireObject<T: JsonSerializable>(_ dict: NSDictionary, asType: T.Type, error: NSErrorPointer) -> T? {
        return T.objectFromJson(jsonObject: dict, error: error) as? T
    }
    
    /// 确保给定字段在字典中存在且是指定对象数组类型
    ///
    /// - parameter dict: 给定字典
    /// - parameter field: 字段名称
    /// - parameter asType: object鉴定类型
    /// - parameter error: 返回的错误
    /// - returns: 如果指定字段是T类型的数组，则返回T类型的对象数组;否则返回nil
    public static func requireArray<T: JsonSerializable>(_ dict: NSDictionary, field: String, asType: T.Type, error: NSErrorPointer) -> [T]? {
        guard let jsonArray = require(dict, field: field, asType: NSArray.self, error: error) else {
            return nil
        }
        
        var objectArray = [T]()
        for jsonObj in jsonArray {
            if let jsonDict = jsonObj as? NSDictionary {
                if let object = requireObject(jsonDict, asType: T.self, error: error) {
                    objectArray.append(object)
                }
            }
        }
        return objectArray
    }
    
    /// 确保给定json对象是指定对象数组类型
    ///
    /// - parameter object: json对象
    /// - parameter field: 字段名称
    /// - parameter asType: object鉴定类型
    /// - parameter error: 返回的错误
    /// - returns: 如果指定字段是T类型的数组，则返回T类型的对象数组;否则返回nil
    public static func requireArray<T: JsonSerializable>(_ object: Any?, asType: T.Type, error: NSErrorPointer) -> [T]? {
        guard let jsonArray = require(object, asType: Array<NSDictionary>.self, error: error) else {
            return nil
        }
        
        var objectArray = [T]()
        for jsonDict in jsonArray {
            if let object = requireObject(jsonDict, asType: T.self, error: error) {
                objectArray.append(object)
            }
        }
        
        return objectArray
    }
    
    /// Json String转换为json对象
    ///
    /// - parameter jsonString: json字符串
    /// - returns: json对象或者nil
    public static func objectFromJsonString(jsonString: String) -> Any? {
        guard let jsonData = jsonString.data(using: .utf8) else {
            return nil
        }
    
        guard let jsonObject = try? JSONSerialization.jsonObject(with: jsonData, options: .mutableContainers) else {
            return nil
        }
        return jsonObject
    }
    
    /// json对象转换为Json String
    ///
    /// - Parameter object: json对象
    /// - Returns: Json String或者nil
    public static func jsonStringFromObject(object: Any) -> String? {
        guard object is NSArray || object is NSDictionary else {
            return nil
        }
        
        do {
            let jsonData = try JSONSerialization.data(withJSONObject: object, options: JSONSerialization.WritingOptions.prettyPrinted)
            guard let jsonString = NSString.init(data: jsonData, encoding: String.Encoding.utf8.rawValue) else {
                return nil
            }
            return String(jsonString)
        } catch let error {
            print(error.localizedDescription)
            return nil
        }
    }
    
    /// 将data转换为json对象
    ///
    /// - Parameter data: 待转换数据
    /// - Returns:
    public static func dataToObject(_ data: Data) -> Any? {
        do {
            let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers)
            return json
        } catch _ {
            return nil
        }
    }
    
    // 默认的json decoder
    public static let defaultJsonDecoder: JSONDecoder = {
        let decoder = JSONDecoder.init()
        decoder.dateDecodingStrategy = .secondsSince1970
        return decoder
    }()
    
    /// 解码数据
    ///
    /// - Parameters:
    ///   - type: 目标类型
    ///   - data: 待解码的数据
    ///   - decoder: json解码器
    ///   - success: 解码成功后调用的闭包
    ///   - failure: 解码失败后调用的闭包
    public static func decode<T>(type: T.Type,
                                 fromData data: Data,
                                 withDecoder decoder: JSONDecoder = defaultJsonDecoder,
                                 success: @escaping (_ object: T) -> Void,
                                 failure: @escaping (_ error: NSError) -> Void) where T: Decodable {
        do {
            let object = try decoder.decode(type, from: data)
            success(object)
        } catch let error as DecodingError {
            switch error {
            case .keyNotFound(_, let context):
                let codingPath = context.codingPath.compactMap { (codingKey) -> String in
                    return codingKey.stringValue
                    }.joined(separator: ".")
                failure(NSError.init(errorCode: -1, errorDescription: "CodingPath: \(codingPath), error: \(context.debugDescription)"))
            case .dataCorrupted(let context):
                let codingPath = context.codingPath.compactMap { (codingKey) -> String in
                    return codingKey.stringValue
                    }.joined(separator: ".")
                failure(NSError.init(errorCode: -1, errorDescription: "CodingPath: \(codingPath), error: \(context.debugDescription)"))
            case .typeMismatch(_, let context):
                let codingPath = context.codingPath.compactMap { (codingKey) -> String in
                    return codingKey.stringValue
                    }.joined(separator: ".")
                failure(NSError.init(errorCode: -1, errorDescription: "CodingPath: \(codingPath), error: \(context.debugDescription)"))
            case .valueNotFound(_, let context):
                let codingPath = context.codingPath.compactMap { (codingKey) -> String in
                    return codingKey.stringValue
                    }.joined(separator: ".")
                failure(NSError.init(errorCode: -1, errorDescription: "CodingPath: \(codingPath), error: \(context.debugDescription)"))
            default:
                failure(NSError.init(errorCode: -1, errorDescription: "\(error.errorDescription ?? "")"))
            }
        } catch let error {
            failure(error as NSError)
        }
    }
    
    /// 解码数据
    ///
    /// - Parameters:
    ///   - type: 目标类型
    ///   - jsonObject: json对象
    ///   - decoder: json解码器
    ///   - success: 解码成功后调用的闭包
    ///   - failure: 解码失败后调用的闭包
    public static func decode<T>(type: T.Type,
                                 fromJsonObject jsonObject: Any,
                                 withDecoder decoder: JSONDecoder = defaultJsonDecoder,
                                 success: @escaping (_ object: T) -> Void,
                                 failure: @escaping (_ error: NSError) -> Void) where T: Decodable {
        
        guard let jsonStr = JsonHelper.jsonStringFromObject(object: jsonObject) else {
            failure(NSError.init(errorCode: -1, errorDescription: "Transcoding json object to json string failed!"))
            return
        }
        
        guard let data = jsonStr.data(using: .utf8) else {
            failure(NSError.init(errorCode: -1, errorDescription: "Transcoding json string to data failed!"))
            return
        }
        
        do {
            let object = try decoder.decode(type, from: data)
            success(object)
        } catch let error as DecodingError {
            switch error {
            case .keyNotFound(_, let context):
                let codingPath = context.codingPath.compactMap { (codingKey) -> String in
                    return codingKey.stringValue
                    }.joined(separator: ".")
                failure(NSError.init(errorCode: -1, errorDescription: "CodingPath: \(codingPath), error: \(context.debugDescription)"))
            case .dataCorrupted(let context):
                let codingPath = context.codingPath.compactMap { (codingKey) -> String in
                    return codingKey.stringValue
                    }.joined(separator: ".")
                failure(NSError.init(errorCode: -1, errorDescription: "CodingPath: \(codingPath), error: \(context.debugDescription)"))
            case .typeMismatch(_, let context):
                let codingPath = context.codingPath.compactMap { (codingKey) -> String in
                    return codingKey.stringValue
                    }.joined(separator: ".")
                failure(NSError.init(errorCode: -1, errorDescription: "CodingPath: \(codingPath), error: \(context.debugDescription)"))
            case .valueNotFound(_, let context):
                let codingPath = context.codingPath.compactMap { (codingKey) -> String in
                    return codingKey.stringValue
                    }.joined(separator: ".")
                failure(NSError.init(errorCode: -1, errorDescription: "CodingPath: \(codingPath), error: \(context.debugDescription)"))
            default:
                failure(NSError.init(errorCode: -1, errorDescription: "\(error.errorDescription ?? "")"))
            }
        } catch let error {
            failure(error as NSError)
        }
    }
    
    /// 解码数据
    ///
    /// - Parameters:
    ///   - type: 目标类型
    ///   - encodedObject: Data或者Json对象
    ///   - decoder: json解码器
    ///   - success: 解码成功后调用的闭包
    ///   - failure: 解码失败后调用的闭包
    public static func decode<T>(type: T.Type,
                                 from encodedObject: Any?,
                                 withDecoder decoder: JSONDecoder = defaultJsonDecoder,
                                 success: @escaping (_ object: T) -> Void,
                                 failure: @escaping (_ error: NSError) -> Void) where T: Decodable {
        if let data = encodedObject as? Data {
            JsonHelper.decode(type: type, fromData: data, success: success, failure: failure)
        } else if let object = encodedObject {
            JsonHelper.decode(type: type, fromJsonObject: object, success: success, failure: failure)
        } else {
            failure(NSError(errorCode: -1, errorDescription: "nil object"))
        }
    }
}


/// Json对象解析器协议
public protocol JsonParserProtocol {
    /// 把对象解析为指定类型的对象
    ///
    /// - parameter object: 被解析的对象
    /// - parameter asType: 目标类型
    /// - returns: 如果解析成功，返回目标类型的一个对象；否则返回nil
    static func parseObject<T>(object: Any, asType: T.Type) -> T?
}

/// 默认的对象解析器
open class JsonObjectParser: JsonParserProtocol {
    /// 把对象解析为指定类型的对象
    ///
    /// - parameter object: 被解析的对象
    /// - parameter asType: 目标类型
    /// - returns: 如果解析成功，返回目标类型的一个对象；否则返回nil
    public class func parseObject<T>(object: Any, asType: T.Type) -> T? {
        if let objectWithType = object as? T {
            return objectWithType
        } else {
            if object is NSNull {
                return nil
            } else if let stringObject = object as? String {
                var convertedObject: Any? = nil
                if T.self == Int.self {
                    convertedObject = Int(stringObject)
                } else if T.self == UInt.self {
                    convertedObject = UInt(stringObject)
                } else if T.self == Float.self {
                    convertedObject = Float(stringObject)
                } else if T.self == Double.self {
                    convertedObject = Double(stringObject)
                } else if T.self == Date.self {
                    convertedObject = stringObject.dateValue()
                }
                
                if nil != convertedObject {
                    return (convertedObject as? T)
                }
            } else if let jsonSerialiableType = asType as? JsonSerializable.Type {
                var error: NSError? = nil
                return jsonSerialiableType.objectFromJson(jsonObject: object, error: &error) as? T
            }
        }
        return nil
    }
}

extension String: JsonSerializable {
    
    /// 对象转化为json对象
    ///
    /// - parameter error: 错误信息
    /// - return: json对象或者nil
    public func toJson(error: NSErrorPointer) -> Any? {
        return self
    }
}
